///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_USIGNAL2_H_
#define _BUOLA_UTILITY_USIGNAL2_H_

#include <buola/utility/uslot.h>
#include <buola/utility/placeholders.h>

///\todo add synchronization with atomics

namespace buola {

namespace detail {

    struct SSignalNoTracking
    {
        template<typename tVector>
        void TrackConnect(const tVector&) {}
        template<typename tVector>
        void TrackDisconnect(const tVector&) {}
    };

    struct SSignalTracking
    {
        void Track(const std::function<void(bool)> pFunction)   {   mFunction=pFunction;    }

        template<typename tVector>
        void TrackConnect(const tVector &pV)
        {
            if(pV.size()==1&&mFunction)
            {
                mFunction(true);
            }
        }
        
        template<typename tVector>
        void TrackDisconnect(const tVector &pV)
        {
            if(pV.empty()&&mFunction)
            {
                mFunction(false);
            }
        }

    private:
        std::function<void(bool)> mFunction;
    };
    
/*namespace detail*/ }

template<typename tSig,typename tTracking=detail::SSignalNoTracking>
class USignal : public tTracking
{
    typedef typename std::function<tSig>::result_type tReturn;
    typedef USlot<tSig>* tConnection;
public:
    USignal()
    {}

    USignal(const USignal&)=delete;
    USignal& operator=(const USignal&)=delete;

    ~USignal()
    {
        for(auto i=mSlots.begin();i!=mSlots.end();i++)
            delete *i;
    }

    template<typename tFunc>
    tConnection Connect(tFunc &&pFunc)
    {
        USlot<tSig> *lSlot=new USlot<tSig>(std::forward<tFunc>(pFunc));
        mSlots.push_back(lSlot);
        tTracking::TrackConnect(mSlots);
        return lSlot;
    }

    template<typename tOtherSig>
    tConnection Connect(USignal<tOtherSig> &pSig)
    {
        USlot<tSig> *lSlot=new USlot<tSig>(std::ref(pSig));
        mSlots.push_back(lSlot);
        tTracking::TrackConnect(mSlots);
        return lSlot;
    }

    template<typename tRet,typename tClass,typename tObj,typename... tFArgs,typename... tArgs>
    tConnection Connect(tRet(tClass::*pFn)(tFArgs...),tObj *pObj,tArgs&&... pArgs)
    {
        static_assert(sizeof...(tFArgs)==sizeof...(tArgs),"function parameter list must have same size as arguments");
        return Connect(std::bind(pFn,pObj,std::forward<tArgs>(pArgs)...));
    }

    template<typename tRet,typename tClass,typename tObj,typename... tFArgs,typename... tArgs>
    tConnection Connect(tRet(tClass::*pFn)(tFArgs...),const intrusive_ptr<tObj> &pObj,tArgs&&... pArgs)
    {
        static_assert(sizeof...(tFArgs)==sizeof...(tArgs),"function parameter list must have same size as arguments");
        return Connect(std::bind(pFn,pObj.get(),std::forward<tArgs>(pArgs)...));
    }

    std::size_t Count()  {   return mSlots.size();   }
    bool Empty()    {   return mSlots.empty();  }

private:
    template<typename tResult,typename... tArgs>
    typename std::enable_if<std::is_same<tResult,void>::value,void>::type DoCall(tArgs&&... pArgs)
    {
        bool lErased=false;
        for(auto i=mSlots.begin();i!=mSlots.end();++i)
        {
            USlot<tSig> *lSlot=*i;
            if(!lSlot->mDeleted)
                lSlot->mFunction(std::forward<tArgs>(pArgs)...);
            else
            {
                delete *i;
                *i=nullptr;
                lErased=true;
            }
        }
        if(lErased)
        {
            auto lEraseFirst=std::remove(mSlots.begin(),mSlots.end(),(USlot<tSig>*)nullptr);
            mSlots.resize(lEraseFirst-mSlots.begin());
        }
    }

    template<typename tResult,typename... tArgs>
    typename std::enable_if<!std::is_same<tResult,void>::value,tReturn>::type DoCall(tArgs&&... pArgs)
    {
        tReturn lReturn=tReturn();
        bool lErased=false;
        for(auto i=mSlots.begin();i!=mSlots.end();++i)
        {
            USlot<tSig> *lSlot=*i;
            if(!lSlot->mDeleted)
                lReturn=lSlot->mFunction(std::forward<tArgs>(pArgs)...);
            else
            {
                delete *i;
                *i=nullptr;
                lErased=true;
            }
        }
        if(lErased)
        {
            auto lEraseFirst=std::remove(mSlots.begin(),mSlots.end(),(USlot<tSig>*)nullptr);
            mSlots.resize(lEraseFirst-mSlots.begin());
        }

        return lReturn;
    }

public:
    template<typename... tArgs>
    tReturn operator()(tArgs&&... pArgs)
    {
        return DoCall<tReturn>(std::forward<tArgs>(pArgs)...);
    }

private:
    std::vector<USlot<tSig>*> mSlots;
};

template<typename tSig>
using UTrackedSignal=USignal<tSig,detail::SSignalTracking>;

/*namespace buola*/ }

#endif
